﻿/***********************************************************************************************
 COPYRIGHT 2008 Vijeth D

 This file is part of TSP Solver NeuronDotNet Sample.
 (Project Website : http://neurondotnet.freehostia.com)

 NeuronDotNet is a free software. You can redistribute it and/or modify it under the terms of
 the GNU General Public License as published by the Free Software Foundation, either version 3
 of the License, or (at your option) any later version.

 NeuronDotNet is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 See the GNU General Public License for more details.

 You should have received a copy of the GNU General Public License along with NeuronDotNet.
 If not, see <http://www.gnu.org/licenses/>.

 ***********************************************************************************************/

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using NeuronDotNet.Core;
using NeuronDotNet.Core.Initializers;
using NeuronDotNet.Core.SOM;
using ZedGraph;
using NeuronDotNet;

namespace AdvancedTSPSolver
{
    public partial class MainForm : Form
    {
        private double[] xValues;
        private double[] yValues;
        private int citiesCount = 25;
        private int neuronCount = 50;
        private bool showVisualization = true;
        private int trainingCycles = 1000;
        private float cityWidth = 5f;
        private Color cityColor = Color.DarkGoldenrod;

        private static readonly Random randomGenerator = new Random();
        private TrainingSet trainingSet = null;
        private KohonenNetwork network = null;

        DrawableRiver river = null;

        public MainForm()
        {
            InitializeComponent();
            river = new DrawableRiver();
        }

        private void Solve(object sender, EventArgs e)
        {
            try
            {
                this.trainingCycles = Math.Max(1, int.Parse(txtCycles.Text));
            }
            catch (Exception) { }
            finally
            {
                txtCycles.Text = trainingCycles.ToString();
            }
            SetEnablement(false);
            KohonenLayer inputLayer = new KohonenLayer(2);
            KohonenLayer outputLayer = new KohonenLayer(new Size(neuronCount, 1));
            outputLayer.RiverClass = river;
            outputLayer.IsRowCircular = true;

            KohonenConnector connector = new KohonenConnector(inputLayer, outputLayer);

            connector.Initializer = new RandomFunction(0, 100);
            network = new KohonenNetwork(inputLayer, outputLayer);
            network.SetLearningRate(0.3);

            int progress = 0;

            network.EndEpochEvent += new TrainingEpochEventHandler(
                delegate(object senderNetwork, TrainingEpochEventArgs args)
                {
                    trainingProgressBar.Value = ((progress++) * 100) / trainingCycles;
                    if (showVisualization) ShowMap();
                    Application.DoEvents();
                });
            network.Learn(trainingSet, trainingCycles);
            ShowMap();
            SetEnablement(true);
        }

        /// <summary>
        /// Returns city closest to given neuron.
        /// Authors: Savenko Maria, Skorodumov Kirill.
        /// </summary>
        public PointF GetClosestCity(PointF neuron)
        {
            int currentCityIndex = 0;
            double currentDistance = Utils.Distance(neuron, 
                                                    new PointF((float)xValues[0], 
                                                               (float)yValues[0]));
            double newDistance;
            for (int i = 1; i < citiesCount; i++)
            {
               newDistance = Utils.Distance(neuron,
                                            new PointF((float)xValues[i],
                                                       (float)yValues[i]));
               if (newDistance < currentDistance)
               {
                   currentDistance = newDistance;
                   currentCityIndex = i;
               }
            }
            return new PointF((float)xValues[currentCityIndex], 
                              (float)yValues[currentCityIndex]);
        }

        private void ShowMap()
        {
            if (network == null)
            {
                return;
            }

            var xVal = new List<double>();
            var yVal = new List<double>();

            int length = neuronCount + 1;
            int neironNumber = 0;

            PointF previousCity = new PointF();
            PointF currentCity;
            PointF currentNeuron;

            for (int i = 0; i < length - 1; i++, neironNumber++)
            {
                IList<ISynapse> synapses
                  = (network.OutputLayer as KohonenLayer)[neironNumber].SourceSynapses;
                currentNeuron
                  = new PointF((float)synapses[0].Weight, (float)synapses[1].Weight);
                
                currentCity = GetClosestCity(currentNeuron);

                if (Contains(xVal, yVal, currentCity))
                {
                  continue;
                }

                if (i > 0 &&
                    AddBridgeIfNecessary(xVal, yVal, currentCity, previousCity))
                {
                    length++;
                    i++;
                }
                xVal.Add(currentCity.X);
                yVal.Add(currentCity.Y);
                previousCity = currentCity;
            }

            //  to make graph enclosed we add the very first point as last
            //  but before check if current point and first points are divided
            //  by river
            currentCity = new PointF((float)xVal[0], (float)yVal[0]);
            AddBridgeIfNecessary(xVal, yVal, currentCity, previousCity);
            xVal.Add(currentCity.X);
            yVal.Add(currentCity.Y);

            GraphPane pane = citiesGraph.GraphPane;
            pane.CurveList.Clear();

            DrawLandscape();

            citiesGraph.GraphPane.AddCurve("",
                                              xVal.ToArray(),
                                              yVal.ToArray(),
                                              cityColor,
                                              SymbolType.None);

            citiesGraph.Refresh();
        }

        /// <summary>
        /// If two points divided by river then the road should lie through the
        /// bridge.
        /// Authors: Savenko Maria, Skorodumov Kirill.
        /// </summary>
        /// <param name="xVal">X coordinates of city list.</param>
        /// <param name="yVal">Y coordinates of city list.</param>
        /// <param name="currentCity">City to which we want to go.</param>
        /// <param name="previousCity">City from which we go.</param>
        /// <returns>True if bridge was added to the list (we crossed
        /// the bridge).</returns>
        private bool AddBridgeIfNecessary(
          List<double> xVal, List<double> yVal,
          PointF currentCity,
          PointF previousCity)
        {
          if (!river.IfPointsDivided(previousCity, currentCity))
          {
            return false;
          }

          PointF bridge = river.GetClosestBridge(new PointF(previousCity.X,
                                                            previousCity.Y));
          xVal.Add(bridge.X);
          yVal.Add(bridge.Y);

          return true;
        }

        /// <summary>
        /// Checks if city list already contains current city. 
        /// Authors: Savenko Maria, Skorodumov Kirill.
        /// </summary>
        /// <param name="xVal">X coordinates of city list.</param>
        /// <param name="yVal">Y coordinates of city list.</param>
        /// <param name="currentCity">City to check.</param>
        /// <returns>True if such city was found.</returns>
        private bool Contains(
          List<double> xVal, List<double> yVal, PointF currentCity)
        {
          int index = xVal.IndexOf(currentCity.X);

          while (index != -1)
          {
            if (yVal[index] == currentCity.Y)
            {
              return true;
            }
            if (index == xVal.Count)
              break;
            index = xVal.IndexOf(currentCity.X, index + 1);
          }

          return false;
        }

        private void SetEnablement(bool enablement)
        {
            btnStop.Enabled = !enablement;
            btnSolve.Enabled = enablement;
            btnRandomize.Enabled = enablement;
            txtCities.Enabled = enablement;
            txtCycles.Enabled = enablement;
            trainingProgressBar.Value = 0;
        }

        private void Randomize(object sender, EventArgs e)
        {
            try
            {
                this.citiesCount = 
                  Math.Max(2, Math.Min(100, int.Parse(txtCities.Text)));
            }
            catch (Exception) { }
            finally
            {
                this.txtCities.Text = citiesCount.ToString();
            }

            LandscapeGenerator generator = 
              new LandscapeGenerator(randomGenerator);
            river = generator.GenerateRiverWithBridges();

            neuronCount = 2 * citiesCount;

            xValues = new double[citiesCount];
            yValues = new double[citiesCount];

            trainingSet = new TrainingSet(2);
            for (int i = 0; i < citiesCount; i++)
            {
                xValues[i] = randomGenerator.Next(1, 99);
                yValues[i] = randomGenerator.Next(1, 99);
                // to prevent cities being on the river
                if (IsNearTheRiver(xValues[i], yValues[i]))
                {
                    i--;
                    continue;
                }
                trainingSet.Add(
                  new TrainingSample(new double[] {xValues[i], yValues[i]}));
            }

            DrawLandscape();
        }

        /// <summary>
        /// Checks if city lies near the river
        /// to minimize possibility of intersections
        /// </summary>
        private bool IsNearTheRiver(double cityX, double cityY)
        {
          double riverval;
          if (river.RiverDirection == River.RiverDirections.FromTopToBottom)
          {
            riverval = river.RiverCurve(cityY);
            if (cityX > (river.RiverCurve(cityY) - river.GetRiverWidth()) &&
                cityX < (river.RiverCurve(cityY) + river.GetRiverWidth()))
            {
              return true;
            }
          }
          else
          {
            riverval = river.RiverCurve(cityX);
            if (cityY > (river.RiverCurve(cityX) - river.GetRiverWidth()) &&
                cityY < (river.RiverCurve(cityX) + river.GetRiverWidth()))
            {
              return true;
            }
          }
          return false;
        }

        /// <summary>
        /// Draw cities, rivers and bridges on the map.
        /// </summary>
        private void DrawLandscape()
        {
            citiesGraph.GraphPane.CurveList.Clear();
            LineItem lineItem = citiesGraph.GraphPane.AddCurve("", xValues, yValues, cityColor, SymbolType.Circle);
            lineItem.Line.IsVisible = false;
            lineItem.Symbol.Fill.Type = FillType.Solid;
            lineItem.Symbol.Size = cityWidth;

            river.AddBridgesToGraph(citiesGraph);
            river.AddRiverToGraph(citiesGraph);
            citiesGraph.Refresh();
        }

        private void LoadForm(object sender, EventArgs e)
        {
            GraphPane pane = citiesGraph.GraphPane;
            pane.XAxis.Title.IsVisible = false;
            pane.YAxis.Title.IsVisible = false;
            pane.Legend.IsVisible = false;
            pane.Chart.Fill = new Fill(Color.AntiqueWhite, Color.Honeydew, -45F);
            pane.CurveList.Clear();
            pane.XAxis.Scale.Min = 0;
            pane.XAxis.Scale.Max = 100;
            pane.YAxis.Scale.Min = 0;
            pane.YAxis.Scale.Max = 100;
            pane.Title.Text = "Sales Map";
            pane.XAxis.MajorTic.IsOutside = false;
            pane.XAxis.MinorTic.IsOutside = false;
            pane.YAxis.MajorTic.IsOutside = false;
            pane.YAxis.MinorTic.IsOutside = false;
            pane.XAxis.MajorGrid.IsVisible = true;
            pane.YAxis.MajorGrid.IsVisible = true;
            pane.XAxis.MajorGrid.Color = Color.LightGray;
            pane.YAxis.MajorGrid.Color = Color.LightGray;
            citiesGraph.AxisChange();
            Randomize(this, EventArgs.Empty);
            SetEnablement(true);
        }

        private void ToggleVisualization(object sender, EventArgs e)
        {
            this.showVisualization = chkShowVisualization.Checked;
        }

        private void StopTraining(object sender, EventArgs e)
        {
            if (network != null)
            {
                network.StopLearning();
                ShowMap();
            }
            SetEnablement(true);
        }

        private void MainFormClosing(object sender, FormClosingEventArgs e)
        {
            StopTraining(this, EventArgs.Empty);
        }
    }
}
